package syntax;

import java.util.ArrayList;

import semantic.Attribute;
import semantic.Paraments;
import semantic.Symbol;
import semantic.SymbolTable;

public class HLParserDumpVisitor implements HLParserVisitor{
	private SymbolTable symtab;
	private SymbolTable structTable;
	private SymbolTable methodTable;
	private ArrayList<String> types = new ArrayList<String>();
	private String allname;
	private String declarename;
	private String state;
	private String type;
	private String info;
	private boolean legal;
	private boolean isType;
	private int checkparapoint;
	
	public HLParserDumpVisitor(){
		info = "";
		symtab = new SymbolTable();
		structTable = new SymbolTable();
		methodTable = new SymbolTable();
		types.add("int");types.add("double");types.add("char");types.add("boolean");types.add("string");
		allname = "";
		declarename = "";
		state = "root";
		type = "";
		legal = true;
		isType = false;
		checkparapoint = 0;
	}
	/*self definition*/
	boolean checkType(String type){
		if ( type.endsWith("]")) {
			int index = type.indexOf("[");
			String size = type.substring(index + 1, type.length() - 1);
			if ( Integer.parseInt( size ) == 0 )
				return false;
			String head = type.substring(0,index);
			if ( types.contains(head) || structTable.DeclaredLocally(head) )
				return true;
		}
		else {
			if ( types.contains(type) || structTable.DeclaredLocally(type))
				return true;
		}
		return false;
	}
	
	public String getInfo(){
		return info;
	}
	
	/**************the visits**************************/
	public Object visit(SimpleNode node, Object data) {
		System.out.println("don't find the declaration");
		return data;
	}

	public Object visit(ASTProg node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}
	
	public Object visit(ASTMainFunction node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTStructDeclaration node, Object data) {
		String temp = state;
		state = "structDeclaration";
		allname = "";
		
		data = node.childrenAccept(this, data);
		
		allname = "";
		state = temp;
		return data;
	}

	public Object visit(ASTMethodDefinition node, Object data) {
		String temp = state;
		state = "methodDefinition";
		allname = "";
		type = "";
		legal = true;
		
		Token start = node.jjtGetFirstToken();
		String voidtype = start.image;
		if (voidtype == "void")
			type = "void";
		
		data = node.childrenAccept(this, data);
		
		allname = "";
		state = temp;
		return data;
	}

	public Object visit(ASTType node, Object data) {
		Token start = node.jjtGetFirstToken(),end = node.jjtGetLastToken();
		type = "";
		while ( start != end.next ){
			type += start.image;
			start = start.next;
		}
		start = node.jjtGetFirstToken();
		legal = checkType(type);
		if ( !legal ){
			info += "This Identifier is not a type name : \" " + type+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn + "\n";
		}
		
		isType = true;
		data = node.childrenAccept(this, data);
		isType = false;
		return data;
	}

	public Object visit(ASTFunctionBody node, Object data) {
		allname = "";
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTStatement node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTFunctionCall node, Object data) {
		String temp = state;
		state = "FunctionCall";
		allname = "";
		
		Token start = node.jjtGetFirstToken(),end = node.jjtGetLastToken();
		String methodname = start.image;
		/*get the function parament info*/
		while ( start != end.next ) {
			allname += start.image;
			start = start.next;
		}
		start = node.jjtGetFirstToken();
		data = node.childrenAccept(this, data);
		
		/*see whether the function name exists*/
		if ( !methodTable.DeclaredLocally(methodname) )
			info += "The method " + allname + " at Line: " + start.beginLine + " is undefined \n";
		else {
			/*see whether the function take paraments*/
			Symbol method = methodTable.RetrieveSymbol(methodname);
			String parainfo = allname.substring(allname.indexOf("("),allname.indexOf(")"));
			if (method.getAttr().paras.size() != 0 && parainfo.equals("(")){
				info += "The method " + methodname + method.getAttr().methodBody() +
						" at Line: " + start.beginLine + " is not applicable for the arguments " + 
						allname.substring(allname.indexOf("(") , allname.indexOf(")") + 1) + "\n";
			}
		}
		
		allname = "";
		checkparapoint = 0;
		state = temp;
		return data;
	}

	public Object visit(ASTAssignment node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTVarDeclaration node, Object data) {
		String temp = state;
		state = "varDeclaration";
		type = "";
		declarename = "";
		legal = true;
		Token start = node.jjtGetFirstToken();
		
		data = node.childrenAccept(this, data);
		
		declarename = "";
		state = temp;
		return data;
	}

	public Object visit(ASTNonDeclarationAssignment node, Object data) {
		String temp = state;
		state = "NonDeclaration";
		type = "";
		legal = true;
		
		data = node.childrenAccept(this, data);
		
		state = temp;
		return data;
	}

	public Object visit(ASTBlock node, Object data) {
		symtab.OpenScope();
		
		data = node.childrenAccept(this, data);
		
		symtab.CloseScope();
		return data;
	}

	public Object visit(ASTJumpStatement node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTIfStatement node, Object data) {
		String temp = state;
		state = "If";
		
		data = node.childrenAccept(this, data);
		
		state = temp;
		return data;
	}

	public Object visit(ASTWhileStatement node, Object data) {
		String temp = state;
		state = "While";
		
		data = node.childrenAccept(this, data);
		
		state = temp;
		return data;
	}

	public Object visit(ASTForStatement node, Object data) {
		String temp  = state;
		state = "For";
		
		data = node.childrenAccept(this, data);
		
		state = temp;
		return data;
	}

	public Object visit(ASTPrintStatement node, Object data) {
		String temp = state;
		state = "Print";
		
		data = node.childrenAccept(this, data);
		
		state = temp;
		return data;
	}

	public Object visit(ASTInputStatement node, Object data) {
		String temp = state;
		state = "Input";
		
		data = node.childrenAccept(this, data);
		
		state = temp;
		return data;
	}

	public Object visit(ASTExpression node, Object data) {
		String in = (String) node.childrenAccept(this, data);
		Token start = node.jjtGetFirstToken();
		String[] ins =  in.split(",");
		if ( state == "FunctionCall" ){
			String name = allname.substring(0, allname.indexOf("("));
			Symbol func = methodTable.RetrieveSymbol(name);
			String[] paras = allname.split(",");
			if (func.getAttr().paras.size() != paras.length || !func.getAttr().paras.get(checkparapoint).paraType.equals(ins[0]))
				info += "The method " + name + func.getAttr().methodBody() +
						" at Line: " + start.beginLine + " is not applicable for the arguments " + 
						allname.substring(allname.indexOf("(") , allname.indexOf(")") + 1) + "\n";
			checkparapoint++;
		}
		else if ( state == "varDeclaration" ) {
			if ( type.equals(ins[0]) && ins[1].equals("true")) {
				Symbol sym = symtab.RetrieveSymbol(declarename);
				sym.getAttr().initialized = true;
			}
			else if ( !type.equals(ins[0]) ) {
				info += "type wrong";
			}
			else if ( !ins[1].equals("true")) {
				info += "assign";
			}
		}
		else if ( state == "NonDeclaration" ) {
			
		}
		data = "";
		return data;
	}

	public Object visit(ASTOrExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		String[] ins = in.split(",");
		if ( node.children.length > 1 ) {
			if ( ins.length == 2 ){
				if ( ins[0].equals("error") )
					info += "Type mismatch: the types of the two expressions are diffrent at Line: " + 
							start.beginLine + "\n";
				else if ( ! (ins[0].equals("boolean") ) )
					info += "The operator || is undefined for the argument type(s) " + ins[0] + ", " +ins[0] +
							" at Line: " + start.beginLine + "\n";
			}
		}
		data = in;
		return data;	
	}

	public Object visit(ASTAndExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		String[] ins = in.split(",");
		if ( node.children.length > 1) {
			if ( ins.length == 2 ){
				if ( ins[0].equals("error") )
					info += "Type mismatch: the type of the two expressions are diffrent at Line: " + 
							start.beginLine + "\n";
				else if ( ! (ins[0].equals("boolean") ) )
					info += "The operator && is undefined for the argument type(s) " + ins[0] + ", " +ins[0] + 
							" at Line: " + start.beginLine + "\n";
			}
		}
		data = in;
		return data;
	}

	public Object visit(ASTEqualityExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		data = in;
		String[] ins = in.split(",");
		if ( node.children.length > 1) {
			if ( ins.length == 2 ){
				if ( ins[0].equals("error") )
					info += "Type mismatch: the type of the two expressions are diffrent at Line: " + 
							start.beginLine + "\n";

			}
		}
		return data;
	}

	public Object visit(ASTRelationalExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		data = in;
		String[] ins = in.split(",");
		if ( node.children.length > 1) {
			Token temp = ((SimpleNode) node.children[1]).jjtGetFirstToken();
			if ( ins.length == 2 ){
				if ( ins[0].equals("error") )
					info += "Type mismatch: the type of the two expressions are diffrent at Line: " + 
							start.beginLine + "\n";
				else if ( !(ins[0].equals("int") || ins[0].equals("double")) )
					info += "The operator " + temp.image + " is undefined for the argument type(s) " 
							+ ins[0] + ", " +ins[0] + " at Line: " + start.beginLine + "\n";
			}
		}
		return data;
	}

	public Object visit(ASTAddExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		data = in;
		String[] ins = in.split(",");
		if ( node.children.length > 1) {
			Token temp = ((SimpleNode) node.children[1]).jjtGetFirstToken();
			if ( ins.length == 2 ){
				if ( ins[0].equals("error") )
					info += "Type mismatch: the type of the two expressions are diffrent at Line: " + 
							start.beginLine + "\n";
				else if ( !(ins[0].equals("int") || ins[0].equals("double")) )
					info += "The operator " + temp.image + " is undefined for the argument type(s) " 
							+ ins[0] + ", " +ins[0] + " at Line: " + start.beginLine + "\n";
			}
		}
		return data;
	}

	public Object visit(ASTMultiplyExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		data = in;
		String[] ins = in.split(",");
		if ( node.children.length > 1) {
			Token temp = ((SimpleNode) node.children[1]).jjtGetFirstToken();
			if ( ins.length == 2 ){
				if ( ins[0].equals("error") )
					info += "Type mismatch: the type of the two expressions are diffrent at Line: " + 
							start.beginLine + "\n";
				else if ( !(ins[0].equals("int") || ins[0].equals("double")) )
					info += "The operator " + temp.image + " is undefined for the argument type(s) " 
							+ ins[0] + ", " +ins[0] + " at Line: " + start.beginLine + "\n";
			}
		}
		return data;
	}

	public Object visit(ASTUnaryExpression node, Object data) {
		Token start = node.jjtGetFirstToken();
		String in = (String) node.childrenAccept(this, data);
		data = in;
		String[] ins = in.split(",");
		if ( node.children.length > 1) {
			Token temp = ((SimpleNode) node.children[0]).jjtGetFirstToken();
			if ( ins.length == 2 ){
				if ( temp.image.equals("-"))
					if ( !(ins[0].equals("int") || ins[0].equals("double")) )
						info += "The operator " + temp.image + " is undefined for the argument type(s) " 
								+ ins[0] + " at Line: " + start.beginLine + "\n";
				else if ( temp.image.equals("!")){
					System.out.println();
					if ( !(ins[0].equals("boolean")) )
						info += "The operator " + temp.image + " is undefined for the argument type(s) " 
								+ ins[0] + " at Line: " + start.beginLine + "\n";
				}
			}
		}
		return data;
	}

	public Object visit(ASTTerm node, Object data) {
		String temp = state;
		
		Token start = node.jjtGetFirstToken(),end = node.jjtGetLastToken();
		String term = "";
		while (start != end.next){
			term += start.image;
			start = start.next;
		}
		//if (term.contains("."))
		//	state = "struct";
		data = node.childrenAccept(this, data);
		state = temp;
		return data;
	}

	public Object visit(ASTTerm0 node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTPrimaryExpression node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTPrimaryType node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTEqualOp node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTOrOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTAndOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTEqualityOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTCompareOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTAddOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTMulOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTUnaryOption node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTIdentifier node, Object data) {
		Token start = node.jjtGetFirstToken(),end = node.jjtGetLastToken();
		String t = "" , l = "true";
		String name = "";
		while (start != end.next){
			name += start.image;
			start = start.next;
		}
		start = node.jjtGetFirstToken();
		if (state == "methodDefinition") {
			if (allname == ""){//define the method name
				if (legal && !isType){
					Attribute attr = new Attribute(Attribute.methodKind);
					attr.paras = new ArrayList<Paraments>();
					if (!methodTable.DeclaredLocally(name)){
						methodTable.EnterSymbol(name, attr);
						allname = name;
						info += "the returntype of the method \" " + name+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
								+ " is "+ type + "\n";
					}
					else {
						info += "This method is already declared: \" "+ name+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
								+ "\n";
					}
				}
				else {
					if (!isType){
						info += "the returntype of the method \" "+ name+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
						+" is "+ type + " (illegal)!" + "\n";
					}
				}
			}
			else {//define the paras of the method
				if (legal && !isType){
					Symbol method = methodTable.RetrieveSymbol(allname);
					boolean add = true;
					for (int i = 0;i < method.getAttr().paras.size();i++){
						if (method.getAttr().paras.get(i).paraName.equals(name)){
							info += "This parament is already declared: \" " + name + " \" at Line: " + start.beginLine + " , Column: " 
									+ start.beginColumn + "\n";
							add = false;
							break;
						}
					}
					if (add){
						method.getAttr().paras.add(new Paraments(type,name));
						info += "the type of the parament \" "+ name+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
								+ " is "+ type + "\n";
					}
				}
				else{
					if (!isType){
						info += "the type of \" " + name + " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
						+ " is " + type + " (illegal)!" + "\n";
					}
				}
			}
		}
		else if (state == "structDeclaration") {
			Attribute attr = new Attribute(Attribute.structKind);
			attr.paras = new ArrayList<Paraments>();
			if (!structTable.DeclaredLocally(name)){
				structTable.EnterSymbol(name, attr);
				allname = name;
				//System.out.println("the type of the struct "+type+" is struct");
				info += "the type of the struct \" "+ name+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
						+ " is struct" + "\n";
			}
			else {
				//System.out.println("Name cannot be redeclared: " + type);
				info += "Name cannot be redeclared: \" " + name+ " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn + "\n";
				Symbol sym = structTable.RetrieveSymbol(name);
				sym.getAttr().Kind = Attribute.errorKind;
			}
		}
		else if (state == "varDeclaration") {
			Symbol sym = symtab.RetrieveSymbol(name);
			if (symtab.DeclaredLocally(name)){
				info += "This variable is already declared: \" " + name + " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
						+ "\n";
				Attribute attr = new Attribute(Attribute.errorKind);
				if (sym != null)
					sym.setAttr(attr);
			}
			else{
				if (legal && !isType){
					Attribute attr = new Attribute(Attribute.variableKind);
					attr.Type = type;
					if ( allname.equals("") ){
						if ( type.endsWith("]")) {
							int index = type.indexOf("[");
							String size = type.substring(index + 1, type.length() - 1);
							attr.arraySize = Integer.parseInt( size );
						}
						declarename = name;
						info += "the type of \" " + name + " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
								+" is " + type + "\n";
						symtab.EnterSymbol(name,attr);
					}
					else{
						Symbol struct = structTable.RetrieveSymbol(allname);
						boolean add = true;
						for (int i = 0;i < struct.getAttr().paras.size();i++){
							if (struct.getAttr().paras.get(i).paraName.equals(name)){
								info += "This variable is already declared: \" " + name + " \" at Line: " + start.beginLine 
										+ " , Column: " + start.beginColumn+ "\n";
								add = false;
								break;
							}
						}
						if (add){
							struct.getAttr().paras.add(new Paraments(name,type));
							info += "the type of \" "+ name + " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
									+ " is "+ type + "\n";
						}
					}
				}
				else{
					if (!isType){
						info += "the type of \" " + name + " \" at Line: " + start.beginLine + " , Column: " + start.beginColumn
								+ " is " + type + " (illegal)!" + "\n";
					}
				}
			}
		}
		else if (state == "Input" || state == "struct") {
		}
		else {
			if ( state != "FunctionCall" || ( state =="FunctionCall" && !start.next.image.equals("(") )){
				Symbol sym = symtab.RetrieveSymbol(name);
				if (sym == null){
					info += "\" " + name+" \" has not been declared at Line: " + start.beginLine + " , Column: " + start.beginColumn + "\n";
					l = "false";
					t = "error";
				}
				else {
					if (!sym.getAttr().initialized){
						info += "\" " + name+" \" has not been initialized at Line: " + start.beginLine + " , Column: " + start.beginColumn + "\n";
						l = "false";
						t = sym.getAttr().Type;
					}
					else{
						l = "true";
						t = sym.getAttr().Type;
					}
				}
				data = t + "," + l;
			}
		}
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTChar node, Object data) {
		data = node.childrenAccept(this, data);
		data = "char,true";
		return data;
	}

	public Object visit(ASTInteger node, Object data) {
		data = node.childrenAccept(this, data);
		data = "int,true";
		return data;
	}

	public Object visit(ASTDouble node, Object data) {
		data = node.childrenAccept(this, data);
		data = "double,true";
		return data;
	}

	public Object visit(ASTString node, Object data) {
		data = node.childrenAccept(this, data);
		data = "string,true";
		return data;
	}

	public Object visit(ASTBool node, Object data) {
		data = node.childrenAccept(this, data);
		data = "boolean,true";
		return data;
	}

	public Object visit(ASTBreak node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTContinue node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTReturn node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTPrint node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTInput node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTStruct node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTLbracket node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTRbracket node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}

	public Object visit(ASTDot node, Object data) {
		data = node.childrenAccept(this, data);
		return data;
	}
}